/**
 * Copyright (c) 2008 - TheNittyGritty - Thomas Brekelmans
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package nl.thenittygritty.command 
{
	import flash.events.ErrorEvent;
	import flash.events.Event;

	import nl.thenittygritty.command.AbstractCommand;
	import nl.thenittygritty.utils.ArrayUtils;		

	/**
	 * CommandQueue provides addCommand and removeCommand methods which add or 
	 * remove any given command from the queue. CommandQueue extends 
	 * AbstractCommand and executes all its stored commands in the order in 
	 * which they are currently stored as soon as its execute method is called. 
	 * Each command is added to the given command history (if defined) as soon 
	 * as it is succesfully completed.
	 */
	public class CommandQueue extends AbstractCommand 
	{
		private var commands:Array;
		private var currentCommandIndex:uint;
		
		private var commandHistory:CommandHistory;
		
		private var abortOnCommandError:Boolean;

		/**
		 * Creates a new CommandQueue.
		 * 
		 * @param abortOnCommandError Defaults to true and determines whether 
		 * or not the command queue should stop executing as soon as one of its 
		 * stored commands reports an error while executing its execute logic.
		 */
		public function CommandQueue(receiver:ICommandReceiver, 
									 commandHistory:CommandHistory = null, 
									 abortOnCommandError:Boolean = true)
		{
			super(receiver);
			
			commands = new Array();
			currentCommandIndex = 0;
			
			this.commandHistory = commandHistory;
			
			this.abortOnCommandError = abortOnCommandError;
		}

		/**
		 * Executes each command in the queue in the order in which they are 
		 * currently stored.
		 * It is possible to add and remove commands whilst executing the 
		 * commands from the queue but use with care.
		 */
		override public function execute():void
		{
			if (currentCommandIndex < commands.length)
			{
				var currentCommand:AbstractCommand = commands[currentCommandIndex];
				currentCommand.addEventListener(Event.COMPLETE, 
												onCurrentCommandComplete);
				currentCommand.addEventListener(ErrorEvent.ERROR, 
												onCurrentCommandError);
				
				currentCommandIndex++;
				
				currentCommand.execute();
			}
			else
			{
				onCommandComplete();	
			}
		}

		/**
		 * Adds a given command to the end of the queue.
		 */
		public function addCommand(command:ICommand):void
		{
			commands.push(command);
		}

		/**
		 * Removes a given command from the queue if it exists in it.
		 */
		public function removeCommand(command:ICommand):void
		{
			var commandIndex:int = ArrayUtils.getItemIndex(command, commands);
			if (commandIndex != -1)
			{
				commands.splice(commandIndex, 1);
			}
		}
		
		/**
		 * Handles the current command complete event, adds the executed command 
		 * to the command history (if defined) and executes the next command.
		 */
		private function onCurrentCommandComplete(e:Event):void
		{
			if (commandHistory != null)
			{
				// currentCommandIndex - 1 because the currentCommandIndex was 
				// incremented in the execute method.
				commandHistory.addCommand(commands[currentCommandIndex - 1]);	
			}
			
			execute();
		}
		
		/**
		 * Handles the current command error event and aborts the queue or 
		 * executes the next command based on the abortOnCommandError value.
		 */
		private function onCurrentCommandError(e:ErrorEvent):void
		{
			if (abortOnCommandError == true)
			{
				onCommandError(e.text);	
			}
			else
			{
				execute();	
			}
		}
		
		/**
		 * Returns the name of the class in a String.
		 */
		public function toString():String 
		{
			return "CommandQueue";
		}
	}
}
